#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <string>

#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/trim.hpp>

#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/exception.h>

#include <sqlite3.h>
#include <getopt.h>

#include "configurations.h"
#include "command.h"

using namespace std;
using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace boost::algorithm;


void init_process();
void ensure_database_exists();
void ensure_basic_table_access_okay();
void ensure_table_structure_exists();
command*		locate_commander(int argc,char** argv);

log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));		


string sqlite_db_file=".fm.sqlite.db";
string log4cxxfile="log4cxx.properties";	
string installation_history_table_name="installation_history";
string szSQL="SELECT name FROM sqlite_master WHERE type='table' and name=? limit 1";	

string sql_to_create_installation_history_table="drop table if exists installation_history;create table installation_history(id integer  primary key autoincrement, datetime install_time, version_name varchar(128) , major_version integer, minor_version integer,  patch_version integer);drop table if exists resources_meta_info;create table resources_meta_info (id integer  primary key autoincrement, leaf varchar(4096), parent long, size long, type integer, create_time datetime, access_time  datetime,  modify_time datetime, record_create_time datetime, record_update_time datetime, synchronize_time datetime,	uuid varchar(36),  status integer default 0) ;drop index if exists resource_file_full_path_index ;create unique index if not exists resource_file_full_path_index on resources_meta_info (parent, leaf ) ;drop table if exists  folders_meta_info;create table folders_meta_info (id integer  primary key autoincrement, leaf varchar(4096), parent long, size long, type integer, create_time datetime, access_time  datetime,  modify_time datetime, record_create_time datetime, record_update_time datetime, synchronize_time datetime,	uuid varchar(36),  status integer default 0) ;drop index if exists folder_full_path_index ;create unique index if not exists folder_full_path_index on resources_meta_info (parent, leaf ) ;";

string delete_command_type="delete";
string search_command_type="search";
string stats_command_type="stats";



int main(int argc, char** argv)
{	
	PropertyConfigurator::configure(log4cxxfile.c_str());	
	LOG4CXX_INFO(logger, "Entering application.");	
	init_process();	
	
	
	command *cmd=locate_commander(argc, argv);
	
	if(cmd==NULL)
	{
		LOG4CXX_DEBUG(logger,"no suitable command, return ");
		return 1;
	}
	
	cmd->execute();
	
	delete cmd;	
	
	return 0;	
}



void init_process()
{
	ensure_database_exists();	
	ensure_basic_table_access_okay();
	ensure_table_structure_exists();
	LOG4CXX_INFO(logger,"INIT SQLITE CONNECTION OKAY");		 
}



void log_sqlite_open_error(int error_code)
{
	char* error_message=strerror(error_code);
	LOG4CXX_FATAL(logger,string("ERROR MESSAGE WHEN CHECKING FILE EXISTENCE:")+error_message);
	switch(error_code)
	{
		case	EACCES:
		{
			LOG4CXX_FATAL(logger,"ACCESS DEDINED FOR SQLITE FILE");
			return ;
		}
		case	EAGAIN:
		{
			LOG4CXX_FATAL(logger,"ACCESS FAILTURE TO SQLITE FILE");
			return ;			
		}
		
		default:
		{
			LOG4CXX_FATAL(logger,"ACCESS FAILTURE TO SQLITE FILE");
			return ;		
		}
	}
	
	
}
void ensure_database_exists()
{
/**
*check file existence
*/
	int fd=open(sqlite_db_file.c_str(),O_APPEND);
	if(fd>0)
	{
		LOG4CXX_INFO(logger,string("DATABASE FILE EXISTS:") +sqlite_db_file);
		close(fd);
		return ;
	}

/**
*log errors
*/		
	log_sqlite_open_error(errno);
/**
*try to create an empty file 
*/	
	mode_t fc_mode=S_IRUSR|S_IWUSR;
	fd=open(sqlite_db_file.c_str(),O_CREAT,fc_mode);
	if(fd>0)
	{
		LOG4CXX_INFO(logger,string("DATABASE FILE CREATION OKAY:") +sqlite_db_file);
		close(fd);
		return ;
	}
	
	
	LOG4CXX_FATAL(logger,string("DATABASE FILE CREATION FAIL:") +sqlite_db_file);
	exit(1);
	return ;	
}


void ensure_basic_table_access_okay()
{
	sqlite3 *db;
	int open_result=	sqlite3_open(sqlite_db_file.c_str(), &db);	 
	if(open_result!=SQLITE_OK)
	{
	 	LOG4CXX_FATAL(logger,	string("OPEN SQLITE FILE FAILED:")+	sqlite_db_file);	 	
		exit(2);
	}	
	sqlite3_close(db);	
}


void init_tables()
{
	sqlite3 *db;
	int open_result=	sqlite3_open(sqlite_db_file.c_str(), &db);	 
	if(open_result!=SQLITE_OK)
	{
	 	LOG4CXX_FATAL(logger,	string("OPEN SQLITE FILE FAILED:")+	sqlite_db_file);	 	
		exit(2);
	}
	
	char error[1024];
	char *pe=error;
	sqlite3_exec(db,sql_to_create_installation_history_table.c_str(),NULL, NULL, &pe);
	
	sqlite3_close(db);  	
}

void ensure_table_structure_exists()
{
	sqlite3 *db;
	int open_result=	sqlite3_open(sqlite_db_file.c_str(), &db);	 
	if(open_result!=SQLITE_OK)
	{
	 	LOG4CXX_FATAL(logger,	string("OPEN SQLITE FILE FAILED:")+	sqlite_db_file);	 	
		exit(2);
	}
	
	
	sqlite3_stmt *stmt;
	const char *pzTest;
  	int rc = sqlite3_prepare_v2(db, szSQL.c_str(), strlen(szSQL.c_str()), &stmt, &pzTest);	
  	if(rc!=SQLITE_OK)
  	{
  		LOG4CXX_FATAL(logger,string("failure to prepare the statment:")+ szSQL);
		exit(3);
  	}  	
  	LOG4CXX_DEBUG(logger,"sqlite3_prepare_v2 okay");
  	
  	int rc_bind=sqlite3_bind_text(
  		stmt,
  		1,
  		installation_history_table_name.c_str(),
  		installation_history_table_name.length()
  		,NULL
  		);
  		  		
  	if(rc_bind!=SQLITE_OK)
  	{
  		LOG4CXX_FATAL(logger,string("CHECK INSTALLATION HISTORY TABLE EXISTENCE FAILURE[sqlite3_bind_text]:")+sqlite3_errmsg(db)	);
  		exit(4);
  	}
  	
  	int execute_step=sqlite3_step(stmt);
  	if(execute_step==SQLITE_ROW)
  	{
  		/**
  		*table exists
  		*/
  		LOG4CXX_DEBUG(logger,"CHECK INSTALLATION HISTORY TABLE EXISTENCE OKAY[sqlite3_step]");
		const unsigned char *table_name=  sqlite3_column_text(stmt, 0);
  		LOG4CXX_DEBUG(logger,string("INSTALLATION HISTORY TABLE:")+reinterpret_cast<const char*>(table_name)  );		
  		  		  		
  	}else
  	{
  		/**
  		*table does not exist
  		*/
  		LOG4CXX_INFO(logger,"INSTALL HISTORY TABLE DOES NOT EXIST");
  		init_tables();  		
  	}
  	
	sqlite3_close(db);	
}



void init_stats_command(statistics_command *dc,int argc, char** argv)
{
    int option_index = 0;
    int verbose_flag;
    int brief_flag;
    
	struct option long_options[] =
             {
               {"verbose", no_argument,       &verbose_flag, 1},
               {"brief",   no_argument,       &brief_flag, 0},
               {"command",  required_argument, 0, 'c'},
               {"tree",  required_argument, 0, 't'},
               {"filter",  required_argument, 0, 'f'},
               {"mode",  required_argument, 0, 'm'},
               {0, 0, 0, 0}
             };
	char c=-1;
	while( 	(c= getopt_long (argc, argv, "c:t:f:m:",long_options, &option_index) )!=-1	)
	{
		LOG4CXX_DEBUG(logger,c);		
		switch(c)
		{
			case	't':
			{
				string tree=optarg;
				trim(tree);
				LOG4CXX_DEBUG(logger,"tree:"+tree);	
				dc->setTree(tree);	
				LOG4CXX_DEBUG(logger, "tree set:"+dc->getTree());
				break;
			}
			case	'f':
			{
				string filter=optarg;
				trim(filter);
				LOG4CXX_DEBUG(logger,"filter:"+filter);	
				dc->setFilter(filter);	
				LOG4CXX_DEBUG(logger, "filter set:"+dc->getFilter());		
				break;
			}			
			default:
			{
				LOG4CXX_DEBUG(logger,"UNKOWN COMMAND");
				break;
			}
		}
	}
	
}

void init_delete_command(delete_command *dc,int argc, char** argv)
{
    int option_index = 0;
    int verbose_flag;
    int brief_flag;
    
	struct option long_options[] =
             {
               {"verbose", no_argument,       &verbose_flag, 1},
               {"brief",   no_argument,       &brief_flag, 0},
               {"command",  required_argument, 0, 'c'},
               {"tree",  required_argument, 0, 't'},
               {"filter",  required_argument, 0, 'f'},
               {"mode",  required_argument, 0, 'm'},
               {0, 0, 0, 0}
             };
	char c=-1;
	while( 	(c= getopt_long (argc, argv, "c:t:f:m:",long_options, &option_index) )!=-1	)
	{
		LOG4CXX_DEBUG(logger,c);		
		switch(c)
		{
			case	't':
			{
				string tree=optarg;
				trim(tree);
				LOG4CXX_DEBUG(logger,"tree:"+tree);	
				dc->setTree(tree);	
				LOG4CXX_DEBUG(logger, "tree set:"+dc->getTree());
				break;
			}
			case	'f':
			{
				string filter=optarg;
				trim(filter);
				LOG4CXX_DEBUG(logger,"filter:"+filter);	
				dc->setFilter(filter);	
				LOG4CXX_DEBUG(logger, "filter set:"+dc->getFilter());				
			}
			
			default:
			{
				LOG4CXX_DEBUG(logger,"UNKOWN COMMAND");
				break;
			}
		}
	}
	
}

command*		locate_commander(int argc,char** argv)
{
	int c=-1;
    char *avalue = NULL;
    int verbose_flag;
    int brief_flag;
	
	struct option long_options[] =
             {
               {"verbose", no_argument,       &verbose_flag, 1},
               {"brief",   no_argument,       &brief_flag, 0},
               {"command",  required_argument, 0, 'c'},
               {0, 0, 0, 0}
             };
    int option_index = 0;
    c = getopt_long (argc, argv, "c",long_options, &option_index);
    if(c=='c')
    {
    	LOG4CXX_DEBUG(logger,optarg);    	
    	string command_type=   string(optarg); 	 
		LOG4CXX_INFO(logger, command_type);
		
		if(boost::iequals(command_type,delete_command_type) )
		{
			LOG4CXX_DEBUG(logger,"delete-command");	
			delete_command *dc=new delete_command();
			init_delete_command(dc,argc,argv);			
			return dc;								
		}else
		if(boost::iequals(command_type,search_command_type) )
		{
			LOG4CXX_DEBUG(logger,"search-command");		
		}else
		if(boost::iequals(command_type,stats_command_type) )		
		{
			LOG4CXX_DEBUG(logger,"stats-command");							
			
			statistics_command *sc=new statistics_command();
			init_stats_command(sc, argc, argv);			
			return sc;			
		}else
		{
			LOG4CXX_ERROR(logger,"other-command");									
		}
		
    }else
    {
    	LOG4CXX_DEBUG(logger,"non-c");    	    
    }
    
    
                                                  
	return NULL;
}



